#!/usr/bin/env python3
# coding: utf-8
"""
last edited at 2019-8-16
by Stone at BlueNet

提供了State类，用途是在主程序中保存全局状态，由传感程序收集信息并向State对象更新状态，其他控制程序则通过
State对象获取状态，生成控制指令。

参考：
multiprocessing.manager相关资料：廖雪峰-分布式进程：https://www.liaoxuefeng.com/wiki/1016959663602400/1017631559645600#0
使用manager的目的是在多个进程间共享State对象，在Windows下测试失败，在Linux下运行成功。资料中作者使用的系统
为MacOS。
"""
from copy import copy, deepcopy
from multiprocessing.managers import BaseManager

import config as cfg


# ===== ===== ===== ===== =====
# FUNCTIONS
# ===== ===== ===== ===== =====
def get_state_wrapper(state_):
    """闭包，将一个State对象保存在该get_state()的namespace中。"""
    def get_state():
        return state_
    return get_state


def init():
    """在主进程(main.py or robot.py)中调用此函数初始化，以启动
    服务进程。在子进程中，导入本模块，以复用StateManager()对象，
    并自行register get_state()函数和创建StateManager()对象。

    若要共享state，则所有的state都要从State Manager()的实例的
    get_state()方法获得。

    栗子:
    # ===== 主进程.py =====
    import state
    state.init()

    # ===== 子进程1.py =====
    from state import StateManager
    import config as cfg

    StateManager.register('get_state')  # 由于state从网络获取，故注册时只提供名字
    manager = StateManager(address=(cfg.MANAGER_IP, cgf.MANAGER_PORT), authkey=cfg.MANAGER_AUTHKEY)
    manager.connect()
    state = manager.get_state()  # 获取state对象，该对象在主进程的state.init()中创建，在所有进程间共享

    state.set_pos_ball((x, y))

    # ===== 子进程2.py =====
    from state import StateManager
    import config as cfg

    StateManager.register('get_state')
    manager = StateManager(address=(cfg.MANAGER_IP, cgf.MANAGER_PORT), authkey=cfg.MANAGER_AUTHKEY)
    manager.connect()
    state = manager.get_state()

    pos_ball = state.get_pos_ball()
    """
    global manager
    get_state = get_state_wrapper(State())
    StateManager.register('get_state', callable=get_state)

    manager = StateManager(
        address=deepcopy((cfg.MANAGER_IP, cfg.MANAGER_PORT)),
        authkey=copy(cfg.MANAGER_AUTHKEY)
    )
    manager.start()


# ===== ===== ===== ===== =====
# CLASSES
# ===== ===== ===== ===== =====
class StateManager(BaseManager):
    """
    (Linux下测试)若主进程中manager关闭(主进程结束或者manager.shutdown())，则子进程中读取manager所共享对象时可能会抛EOFError。
    """
    pass


class State(object):
    def __init__(self,):
        self.pos_ball = (0, 0)  # it might use numpy array in the future
        self.pos_paw = (0, 0)
        self._is_ball_new = False  # Is the pos_ball the latest one?
        self._is_paw_new = False  # Is the pos_paw the latest one?
        self._recog_task = None  # which task the recognizer to execute

    @property
    def is_ball_new(self):
        return self._is_ball_new

    def set_pos_ball(self, pos):
        """
        Update pos_ball.
        :param pos: The new post of pos_ball.
        :return: None.
        """
        if len(pos) == 2:  # accept 2-D pos
            self.pos_ball = copy(tuple(pos))
            self._is_ball_new = True
        else:
            raise ValueError("A 2-D pos is required.")

    def get_pos_ball(self):
        feedback = (self.pos_ball, self.is_ball_new)
        self._is_ball_new = False

        return feedback

    def set_recog_task(self, task):
        self._recog_task = task

    def get_recog_task(self):
        return self._recog_task


if __name__ == '__main__':
    s = State()
    print('false is new: ', s.is_ball_new)

    s.set_pos_ball((1, 1))
    print('true is new: ',  s.is_ball_new)

    p = s.get_pos_ball()
    print('false is new: ', s.is_ball_new)

    # # 需同时运行controller.py进行测试
    # init()
    # state = manager.get_state()
    #
    # # 本程序相当于服务器，其他进程需通过本服务器获取state对象，如果程序退出，则相当于服务器关闭
    # # 其他程序将无法获得state对象。
    # print('server start ...')
    # import time
    # for i in range(100):
    #     state.set_pos_ball((0, i))
    #     time.sleep(1)
    #
    # print('server end.')


